﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using VIX.Enums;
using VixCOM;

namespace VIX
{
    /// <summary>
    /// Provides object-orientated access to the VIX-API functions.
    /// </summary>
    /// <remarks>
    /// <list type="bullet">
    /// <item>VIX-API Version used in this implementation: 1.10</item>
    /// <item>Documentation used for the comments: http://www.vmware.com/support/developer/vix-api/vix110_reference/ </item>
    /// <item>The class is implemented with the Singleton pattern, to ensure only one instance of the underlying VIX API handle is created per application. Access the functions by using the static <see cref="Instance"/> property.</item>
    /// <item>Typically you start by calling the <see cref="Connect"/> function, which returns a <see cref="Host"/> object allowing you to perform various operations like opening a VM or registering a VM with the host.</item>
    /// </list>
    /// </remarks>
    /// <example>
    /// <para>Here is an example of how to connect to a VMware host and getting an instance of the <see cref="Host"/> class.</para>
    /// <code>
    /// VIX.Host hostObject = VIX.VixCOMWrapper.Instance.Connect(VIX.Enums.VIX_SERVICEPROVIDER.VMWARE_VI_SERVER, "https://servername:8333/sdk", 0, "Username", "Password");
    /// </code>
    /// <para></para>
    /// </example>
    /// <author>Alexander Köplinger, John Graf</author>
    /// <date>Last Update: August 2010</date>
    public class VixCOMWrapper
    {
        #region Private Fields
        /// <summary>
        /// Static instance of this class. 
        /// The goal is to ensure that only one instance is created at any time, preventing problems when more than one VixLibClass-instances are being created.
        /// </summary>
        private static VixCOMWrapper instance = null;
        /// <summary>
        /// Holds an instance of the VixLibClass which can be used to access the VIX-API functions.
        /// </summary>
        private VixLibClass vixLib = null;
        #endregion

        #region Constructor/Destructor
        /// <summary>
        /// Prevents a default instance of the VixWrapper class from being created. Use the Instance property instead.
        /// </summary>
        private VixCOMWrapper()
        {
            this.vixLib = new VixLibClass();
        }

        /// <summary>
        /// Finalizes an instance of the VixWrapper class
        /// </summary>
        ~VixCOMWrapper()
        {
            instance = null;
            this.vixLib = null;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets an instance of the <see cref="VixCOMWrapper"/> class.
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        /// <item>The class is implemented with the Singleton pattern, to ensure only one instance of the underlying VIX API handle is created per application.</item>
        /// <item>Use this static property to access the functions.</item>
        /// </list>
        /// </remarks>
        public static VixCOMWrapper Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new VixCOMWrapper();
                }

                return instance;
            }
        }
        #endregion

        #region VIX-API functions
        /// <summary>
        /// Connects to the host machine.
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        /// <item>To specify the local host (where the API client runs) with VMware Workstation and VMware Player, pass <c>null</c> values for the <paramref name="hostName"/>, <paramref name="hostPort"/>, <paramref name="hostUsername"/>, and <paramref name="hostPassword"/> parameters.</item>
        /// <item>With vCenter Server, ESX/ESXi hosts, and VMware Server 2.0, the URL for the <paramref name="hostName"/> argument may specify the port. Otherwise an HTTPS connection is attempted on port 443. HTTPS is strongly recommended. Port numbers are set during installation of Server 2.0. The installer's default HTTP and HTTPS values are 8222 and 8333 for Server on Windows, or (if not already in use) 80 and 443 for Server on Linux, and 902 for the automation socket, authd. If connecting to a virtual machine though a firewall, port 902 and the communicating port must be opened to allow guest operations.</item>
        /// <item>If a VMware ESX host is being managed by a VMware VCenter Server, you should call Connect with the hostname or IP address of the VCenter server, not the ESX host. Connecting directly to an ESX host while bypassing its VCenter Server can cause state inconsistency.</item>
        /// <item>On Windows, this function should not be called multiple times with different service providers in the same process; doing so will result in a <see cref="VIX_E.WRAPPER_MULTIPLE_SERVICEPROVIDERS"/> error. A single client process can connect to multiple hosts as long as it connects using the same service provider type.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="hostType">The VMware host type you want to connect to.</param>
        /// <param name="hostName">Varies by product platform. With vCenter Server, ESX/ESXi hosts, and VMware Server 2.0, use a URL of the form "https://hostName:port/sdk" where 'hostName' is either the DNS name or IP address. If missing, 'port' may default to 443 (see Remarks). In VIX API 1.10 and later, you can omit "https://" and "/sdk" specifying just the DNS name or IP address. Credentials are required even for connections made locally. With Workstation, <c>null</c> to connect to the local host. With VMware Server 1.0.x, use the DNS name or IP address for remote connections, or the same as Workstation for local connections.</param>
        /// <param name="hostPort">TCP/IP port on the remote host. With VMware Workstation and VMware Player, use zero for the local host. With ESX/ESXi hosts and VMware Server 2.0 you specify port number within the <paramref name="hostName"/> parameter, so this parameter is ignored (see Remarks).</param>
        /// <param name="hostUsername">Username for authentication on the remote machine. With VMware Workstation, VMware Player, and VMware Server 1.0.x, use <c>null</c> to authenticate as the current user on local host. With vCenter Server, ESX/ESXi hosts, and VMware Server 2.0, you must use a valid login.</param>
        /// <param name="hostPassword">Password for authentication on the remote machine. With VMware Workstation, VMware Player, and VMware Server 1.0.x, use <c>null</c> to authenticate as the current user on local host. With ESX/ESXi and VMware Server 2.0, you must use a valid login.</param>
        /// <returns>A <see cref="Host"/> object representing the host.</returns>
        public Host Connect(VIX_SERVICEPROVIDER hostType, string hostName, int hostPort, string hostUsername, string hostPassword)
        {
            IJob jobHandle = this.vixLib.Connect(VixCOM.Constants.VIX_API_VERSION, (int)hostType, hostName, hostPort, hostUsername, hostPassword, 0, null, null);

            object foundProperties = null;

            ulong err = jobHandle.Wait(new VIX_PROPERTY[] { VIX_PROPERTY.JOB_RESULT_HANDLE }, ref foundProperties);
            this.CheckForError(err);

            IHost hostH = (IHost)((object[])foundProperties)[0];
            return new Host(hostH);
        }

        /// <summary>
        /// This method reports what type the object represents: host, virtual machine, job, or snapshot. 
        /// </summary>
        /// <param name="vixHandle">A handle to a VIX-object.</param>
        /// <returns>An enumerated type that identifies what the object represents.</returns>
        internal VIX_HANDLETYPE GetHandleType(IVixHandle vixHandle)
        {
            return (VIX_HANDLETYPE)vixHandle.GetHandleType();
        }

        /// <summary>
        /// This method allows you to get one or more properties. For a list of property IDs, see <see cref="VIX_PROPERTY"/>.
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        /// <item>This function allows you to get one or more properties from the VM, but only specific properties may be defined for this object.</item>
        /// <item>The value of <see cref="VIX_PROPERTY.VM_TOOLS_STATE"/> is valid only after calling <see cref="VM.WaitForToolsInGuest"/>.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="vixHandle">A generic handle to a Vix-object. Can either be IHost, IJob, ISnapshot or IVM</param>
        /// <param name="propertyIDs">An array of property-IDs to identify the properties to fetch.</param>
        /// <returns>A <see cref="System.Collections.Generic.List{T}"/> object that stores the properties identified by the array <paramref name="propertyIDs"/>. Each element in <paramref name="propertyIDs"/> matches the corresponding indentifier in the returned <see cref="System.Collections.Generic.List{T}"/>; for example, if the first element in the <paramref name="propertyIDs"/> array is <see cref="VIX_PROPERTY.VM_IS_RUNNING"/>, the first element in the <see cref="System.Collections.Generic.List{T}"/> will be the object that is the result of this job, if one exists.</returns>
        internal List<object> GetProperties(IVixHandle vixHandle, VIX_PROPERTY[] propertyIDs)
        {
            object foundProperties = null;
            List<object> result = new List<object>();
            ulong err;

            // if the handle is a IJob we must treat it specially, because the properties array may contain arrays too
            if (GetHandleType(vixHandle) == VIX_HANDLETYPE.JOB)
            {
                object prop = null;

                for (int x = 0; x < propertyIDs.Length; x++)
                {
                    int numResults = ((IJob)vixHandle).GetNumProperties((int)propertyIDs[x]);

                    object[] propID = new object[numResults];

                    // loop through each requested property
                    for (int i = 0; i < numResults; i++)
                    {
                        err = ((IJob)vixHandle).GetNthProperties(i, new int[] { (int)propertyIDs[x] }, ref prop);

                        this.CheckForError(err);

                        propID[i] = ((object[])prop)[0];
                    }

                    result.Add(propID);
                }
            }
            // if the handle is not an IJob, we can simply retrieve all properties
            else
            {
                err = vixHandle.GetProperties(propertyIDs, ref foundProperties);

                this.CheckForError(err);

                result.AddRange((object[])foundProperties);
            }

            return result;
        }

        /// <summary>
        /// Given a property ID, this method returns the type of that property.
        /// </summary>
        /// <remarks>
        /// <list type="bullet">
        /// <item>This method identifies the data type of a property.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="vixHandle">A handle to a VIX-object.</param>
        /// <param name="propertyID">The property listed in the enumeration.</param>
        /// <returns>The type of the given property.</returns>
        internal VIX_PROPERTYTYPE GetPropertyType(IVixHandle vixHandle, VIX_PROPERTY propertyID)
        {
            int propertyType;
            ulong err = vixHandle.GetPropertyType((int)propertyID, out propertyType);
            this.CheckForError(err);
            return (VIX_PROPERTYTYPE)propertyType;
        }

        /// <summary>
        /// Checks if a given error-code indicates an error and throws an exception if appropriate.
        /// </summary>
        /// <exception cref="VixException">Thrown when a VIX-API function returns an error.</exception>
        /// <param name="err">The error-code returned by the VIX-API function.</param>
        internal void CheckForError(ulong err)
        {
            if (Instance.vixLib.ErrorIndicatesFailure(err))
            {
                throw new VixException(Instance.vixLib.GetErrorText(err, null), (VIX_E)err);
            }
        }
        #endregion

        #region Not implemented functions

        /// <summary>
        /// Not implemented, because every function is implemented synchroniously instead.
        /// </summary>
        private void CheckCompletion() { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented, because every failure throws an exception containing the error description.
        /// </summary>
        private void ErrorCode() { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented, because every failure throws an exception containing the error description.
        /// </summary>
        private void ErrorIndicatesFailure() { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented, because every failure throws an exception containing the error description.
        /// </summary>
        private void ErrorIndicatesSuccess() { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented, because every error throws an exception.
        /// </summary>
        private void GetError() { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented, because every error throws an exception containing the error description.
        /// </summary>
        private void GetErrorText() { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented, because all available properties are returned in a list which makes it unnecessary to get a specific property.
        /// </summary>
        private void GetNthProperties() { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented, because all available properties are returned in a list which makes it unnecessary to get the number of properties.
        /// </summary>
        private void GetNumProperties() { throw new NotImplementedException(); }

        /// <summary>
        /// Not implemented here, because it is already used in every function to make it synchroniously.
        /// </summary>
        private void WaitWithoutResults() { throw new NotImplementedException(); }

        #endregion
    }
}
